package main

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"log"
	"time"
)

type Client struct {
	hub   *Hub            // 当前客户端所属的hub
	conn  *websocket.Conn // 底层的websocket连接
	send  chan []byte     // 发送消息的通道
	user  string          // 客户端用户名
	rooms map[string]bool // 客户端加入的房间
}

func (c *Client) reader() {
	defer func() { // 清理函数，在reader退出时调用，用于注销客户端并关闭连接
		c.hub.unregister <- c
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait)) // 设置读取超时时间，超过该时间未收到消息则认为连接已断开
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(pongWait)) // 当服务端收到客户端的心跳包时，重置读取超时时间
		return nil
	})
	// 开始监听
	for {
		_, data, err := c.conn.ReadMessage()
		if err != nil {
			// 处理连接异常关闭错误，如连接被强制关闭等
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
				log.Printf("error: %v", err)
			}
			break
		}
		// 解析客户端发送的消息
		var msg Message
		err = json.Unmarshal(data, &msg) // 解析客户端发送的消息
		if err != nil {
			log.Printf("error: %v", err)
			continue
		}

		switch msg.Type {
		case "join":
			room := msg.Room
			if room == "" {
				log.Printf("join room error: room is empty")
				continue
			}
			if _, ok := c.hub.rooms[room]; !ok {
				c.hub.rooms[room] = make(map[*Client]bool) // 如果房间不存在，则创建
			}
			c.hub.rooms[room][c] = true // 加入房间
			c.rooms[room] = true        // 记录客户端加入的房间
		case "leave":
			room := msg.Room
			if clients, ok := c.hub.rooms[room]; ok {
				delete(clients, c)    // 从房间中移除客户端
				delete(c.rooms, room) // 从记录中移除房间
			}
		case "message":
			msg.Time = time.Now().Second()
			b, err := json.Marshal(msg)
			if err != nil {
				log.Printf("error: %v", err)
				return
			}
			if msg.To != "" {
				c.hub.broadcast <- Broadcast{To: msg.To, Data: b} // 发送给指定客户端
			} else if msg.Room != "" {
				c.hub.broadcast <- Broadcast{Room: msg.Room, Data: b} // 广播给房间中的所有客户端
			} else {
				c.hub.broadcast <- Broadcast{Data: b} // 广播给所有客户端
			}
		default:
			log.Printf("unknown message type: %s", msg.Type)
		}
	}
}

func (c *Client) writer() {
	ticker := time.NewTicker(pingPeriod) // 创建一个定时器，用于发送心跳包
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			c.conn.SetReadDeadline(time.Now().Add(writeWait))
			if !ok {
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			w, err := c.conn.NextWriter(websocket.TextMessage) // 获取一个文本消息的写入器
			if err != nil {
				return
			}
			w.Write(message)
			fmt.Printf("send message: %s\n", message)
			// 获取发送通道中的所有消息， 一次性获取所有消息，使用\n换行分隔减少网络IO次数
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write([]byte{'\n'})
				select {
				case m, _ := <-c.send:
					w.Write(m)
				default:
					break
				}
			}
			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C: // 触发定时器，发送心跳包
			c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // 设置写入超时时间，超过该时间未发送成功则认为连接已断开
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}
